//*************************************************************************************
/** \file scope_queue.h
 *    This file implements a simple queue (or circular buffer). Data which is placed in 
 *    the queue is saved for future reading. The queue class is implemented as a 
 *    template; this means that a queue can be configured to store data of almost any
 *    type. This particular version of the queue is modified by adding an extra index
 *    which can be used to read data which is buffered in such a way as to make an
 *    oscilloscope trace. 
 *
 *  Usage:
 *    The template specifies the type of data being stored as qType, the type of
 *    integer used for the index as qIndexType, and the number of data items in the
 *    queue as qSize. First choose the type of data being stored; this may be simple
 *    data types such as characters or integers, but it may also be something more
 *    complex such as objects of a class (if there's enough memory to hold them all).
 *    Then look at the number of items in the queue. If there are less than 255, use
 *    a character as qIndexType; otherwise use a regular 16-bit integer. For example,
 *    a queue holding 100 long integers (which would use 400 bytes for the data plus
 *    a few more bytes for the indices) could be declared as 
 *    "scope_queue<long, char, 100> my_queue". 
 *
 *  Revisions:
 *    \li 08-28-2003 JRR Reference: www.yureu.com/logo_plotter/queue_code.htm
 *    \li 08-28-2003 JRR Ported to C++ from C
 *    \li 12-14-2007 JRR Doxygen comments added
 *    \li 02-17-2008 JRR Changed index type from define to template parameter
 *    \li 08-12-2008 JRR Added overloaded array subscript operator
 *    \li 02-10-2011 JRR Forked a version with pre-trigger buffer for oscilloscope
 *
 *  License:
 *    This file copyright 2007-2011 by JR Ridgely. It is released under the Lesser GNU
 *    public license, version 2. It is intended for educational use only, but the
 *    user is free to use it for any purpose permissible under the LGPL. The author
 *    has no control over the use of this file and cannot take any responsibility
 *    for any consequences of this use. 
 */
//*************************************************************************************

/// These defines prevent this file from being included more than once in a *.cc file
#ifndef _QUEUE_H_
#define _QUEUE_H_


//-------------------------------------------------------------------------------------
/** This class implements a simple queue. As a template class, it can be used to 
 *  create queues which hold any type of object whose data is copied in with an
 *  assignment operator. The size and type of object are determined at compile time
 *  for efficiency. 
 */

template <class qType, class qIndexType, qIndexType qSize> 
class scope_queue
{
	protected:
		qType buffer[qSize];			///< This memory buffer holds the contents
		qIndexType i_put;				///< Index where newest data will be written
		qIndexType i_get;				///< Index where oldest data was written
		qIndexType how_full;			///< How many elements are full at this time
		qIndexType lag;					///< How far is write ahead of read?
		qIndexType max_lag;				///< Maximum buffered points before trigger
		bool been_triggered;			///< True if trigger has been tripped yet
		bool pre_trig_mode;				///< True if pre-trigger data is being saved

	public:
		scope_queue (void);						// Default constructor
		scope_queue (qIndexType);				// Constructor sets pre-buffer size
		void set_pre_buffer_size (qIndexType);	// Set size of pre-trigger buffer
		void jam (qType);						// Force entry even if queue full
		qType get (void);						// Gets an item from the queue
		void flush (void);						// Empty out the whole buffer
		void reread (void);						// Reset indices to resend all data

		/** This method is called when the 'scope is triggered. It sets a flag so the
		 *  read pointer will stop following 'lag' points behind the write pointer and
		 *  data can be saved until the correct number of points have been saved.
		 */
		inline void trigger (void) { been_triggered = true; }

		/** This method returns true if the queue is empty.  Empty means that there 
		 *  isn't any data which hasn't previously been read.
		 *  @return True if the queue has unread data, false if it doesn't
		 */
		inline bool is_empty (void) { return (how_full == 0); }

		/** This method returns true if the queue is full.
		 *  @return True if the queue is full, false if there is empty space left
		 */
		inline bool is_full (void) { return (how_full >= qSize); }

		/** This method puts the queue into pre-triggering mode, in which data taken
		 *  before a trigger will be held. In non-pre-triggering mode, data is saved
		 *  only after a trigger signal arrives.
		 */
		inline void set_pre_trigger_mode (void) { pre_trig_mode = true; }

		/** This method puts the queue into "normal", non pre-triggering mode. Data
		 *  from before the trigger signal arrives will not be held.
		 */
		inline void set_non_pre_trigger_mode (void) { pre_trig_mode = false; }

		// This operator returns an item at the given index in the queue
		qType operator[] (qIndexType);

		/** This method returns the number of items in the queue */
		qIndexType num_items (void) { return (how_full); }
		
		/** This method returns a pointer to the buffer which holds the data in the 
		 *  queue. Normally it's not used, but in some cases (particularly queues of
		 *  characters) one may need to access the data buffer directly.
		 *  @return A pointer to the buffer holding the data in the queue
		 */
		qType* get_p_buffer (void) { return buffer; }

		// Make the function which prints the queue's status a friend so it can access
		// protected member variables. This is not a facebook friend but a C++ friend
		template <class qT, class qIT, qIT qS> friend 
		base_text_serial& operator<< (base_text_serial&, scope_queue<qT, qIT, qS>&);
};


// If the basic serial port device is defined, we can allow the queue to print a
// status line showing its current condition
#ifdef _BASE_TEXT_SERIAL_H_
	template <class qType, class qIndexType, qIndexType qSize>
	base_text_serial& operator<< (base_text_serial& ser, scope_queue<qType, 
								  qIndexType, qSize>& a_queue)
	{
		ser << "Queue N: " << qSize << " rd: " << a_queue.i_get << " wr: " 
			<< a_queue.i_put << " lag: " << a_queue.lag << endl;
		
		return (ser);
	}
#endif // _BASE_TEXT_SERIAL_H_


//-------------------------------------------------------------------------------------
/** This constructor creates a queue item.  Note that it doesn't have to allocate 
 *  memory because that was already done statically by the template mechanism at 
 *  compile time. If no parameters are supplied, a 16-element queue for characters 
 *  which uses 8-bit array indices (for a maximum of 255 elements) is created. 
 *  The template parameters are as follows: 
 *	\li qType: The type of data which will be stored in the queue (default char)
 *	\li qIndexType: The type of data used for array indices
 *	\li qSize: The number of items in the queue's memory buffer (default 16)
 */ 

template <class qType, class qIndexType, qIndexType qSize> 
scope_queue<qType, qIndexType, qSize>::scope_queue (void)
{
	max_lag = qSize / 2;
	flush ();
	pre_trig_mode = false;
	been_triggered = false;
}


//-------------------------------------------------------------------------------------
/** This constructor creates a queue item, given a value for the maximum number of
 *  points which are to be held prior to a trigger event taking place. The constructor
 *  doesn't have to allocate memory because that was already done statically by the 
 *  template mechanism at compile time. If no parameters are supplied, a 16-element 
 *  queue for characters which uses 8-bit array indices (for a maximum of 255 elements)
 *  is created. The template parameters are as follows: 
 *    \li qType: The type of data which will be stored in the queue (default char)
 *    \li qIndexType: The type of data used for array indices
 *    \li qSize: The number of items in the queue's memory buffer (default 16)
 *  @param pre_trig_buf_size The size of the pre-trigger buffer to be used
 */ 

template <class qType, class qIndexType, qIndexType qSize> 
scope_queue<qType, qIndexType, qSize>::scope_queue (qIndexType pre_trig_buf_size)
{
	set_pre_buffer_size (pre_trig_buf_size);
	flush ();
	pre_trig_mode = false;
	been_triggered = false;
}


//-------------------------------------------------------------------------------------
/** This method sets the size of the pre-trigger buffer. The pre-trigger buffer holds
 *  data which were collected before the trigger condition occurred so that those data
 *  can be sent as the first part of a transmitted trace. 
 *  @param buf_size The requested size for the pre-trigger buffer
 */

template <class qType, class qIndexType, qIndexType qSize>
void scope_queue<qType, qIndexType, qSize>::set_pre_buffer_size (qIndexType buf_size)
{
	if (buf_size < qSize)					// Make sure the requested pre-trigger 
	{										// buffer size is less than the size of 
		max_lag = buf_size;					// the buffer itself; if not, make the
	}										// pre-buffer as large as it can be
	else
	{
		max_lag = qSize;
	}
}


//-------------------------------------------------------------------------------------
/** This method empties the buffer. It doesn't actually erase everything; it just sets
 *  the indices and fill indicator all to zero as if the buffer contained nothing.
 */

template <class qType, class qIndexType, qIndexType qSize> 
void scope_queue<qType, qIndexType, qSize>::flush (void)
{
	i_put = 0;
	i_get = 0;
	how_full = 0;
	lag = 0;
	been_triggered = false;
}


//-------------------------------------------------------------------------------------
/** This method jams an item into the queue whether the queue is empty or not.  This 
 *  can overwrite existing data, so it must be used with caution.
 *  @param data The data to be jammed into the queue
 */

template <class qType, class qIndexType, qIndexType qSize> 
void scope_queue<qType, qIndexType, qSize>::jam (qType data)
{
	// Write the data and move the write pointer to the next element
	buffer[i_put] = data;
	if (++i_put >= qSize) 
	{
		i_put = 0;
	}

	// Since this is an oscilloscope queue, we must check if we're buffering pretrigger
	// data. If so, make sure the read pointer follows the write pointer by at most
	// lag_counter data points
	if (pre_trig_mode && (!been_triggered))
	{
		if (lag < max_lag)					// If the pre-trigger buffer isn't yet full
		{									// then leave the read pointer where it is
			lag++;							// and note that one more point is saved
			how_full++;
		}
		else								// If the pre-trigger buffer is full, move
		{									// the read pointer to follow the write
			if (++i_get >= qSize)			// pointer by 'lag' data elements
			{
				i_get = 0;
			}
		}
	}
	// If we're not in pre-trigger mode, revert to normal behavior and fill up the 
	// buffer
	else
	{
		if (how_full >= qSize)				// Check if the buffer is already full; if
		{									// so, the read index has to be moved so 
			if (++i_get >= qSize)			// that it points to the oldest unread data, 
			{								// which isn't the data written now
				i_get = 0;
			}
		}
		else								// The buffer's not full; let read index
		{									// remain at the same value 
			how_full++;					 	// If we get here, the buffer isn't full
		}
	}
}


//-------------------------------------------------------------------------------------
/** This method returns the oldest item in the queue.  If the queue was empty, this is
 *  already-retreived data.  Somebody should have checked if there was new data 
 *  available using the is_empty() method.
 *  @return The data which is pulled out from the queue at the current location
 */

template <class qType, class qIndexType, qIndexType qSize> 
qType scope_queue<qType, qIndexType, qSize>::get (void)
{
	qType whatIgot;							// Temporary storage for what was read

	whatIgot = buffer[i_get];				// Read and hold the data

	if (how_full > 0)						// If the buffer's not empty,
	{										// move the read pointer to the next full
		if (++i_get >= qSize)				// element
		{
			i_get = 0;
		}
		how_full--;							// There's now one less item in the buffer
	}

	return (whatIgot);
}


//-------------------------------------------------------------------------------------
/** This method resets the indices so a buffer full of data can be re-read.  It should
 *  only be called when the buffer has just been emptied without having written any new
 *  data in there.
 */

template <class qType, class qIndexType, qIndexType qSize> 
void scope_queue<qType, qIndexType, qSize>::reread (void)
{
	// Set the read index to be one item past the write index
	i_get = i_put;

	if (++i_get >= qSize)
	{
		i_get = 0;
	}
}


//-------------------------------------------------------------------------------------
/** This overloaded array subscript operator implements subscripts in the queue. The 
 *  index is defined relative to the location at which the oldest data in the queue
 *  was written. This operator returns the (index)-th item in the queue, or (-1) 
 *  typecast to the queue data type if there aren't enough items in the queue to get 
 *  to the given index. The retreived data is not removed from the queue. 
 *  @param index The array index into the queue at which to get a data item
 *  @return The data which is at the given index
 */

template <class qType, class qIndexType, qIndexType qSize> 
qType scope_queue<qType, qIndexType, qSize>::operator[] (qIndexType index)
{
	// Check if there's data written at the given location
	if (index > how_full)
		return ((qType)(-1));

	// Find an index pointing to the correct location in the queue
	qIndexType getIndex = i_get + index;
	if (getIndex > qSize)
		getIndex -= qSize;

	// Get the data at that index location
	return (buffer[getIndex]);
}

#endif // _QUEUE_H_
